package com.ztesoft.zcm.cmdb.util.remote;

import com.ztesoft.zcm.cmdb.util.CmdbExceptionErrorCode;
import com.ztesoft.zsmart.core.exception.BaseAppException;
import com.ztesoft.zsmart.zcm.core.exception.ExceptionPublisher;
import lombok.Builder;
import lombok.Data;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * 远程客户端池
 *
 * @author li.peilong
 * @date 2019/05/22
 **/
@Data
@Builder
public class RemoteClientPool {
    private int minPoolSize;
    private int maxPoolSize;
    private int maxIdleTime;
    /**
     * 获取客户端超时时间,单位：ms
     */
    private int timeout;
    private HostAccessInfo hostAccessInfo;
    private final BlockingQueue<RemoteClientHolder> queue = new LinkedBlockingDeque<>();
    /**
     * 当前创建成功的客户端
     */
    private final List<RemoteClient> clients = new CopyOnWriteArrayList<>();
    /**
     * 当前客户端数，包括创建成功以及正在创建的
     */
    private final AtomicInteger counting = new AtomicInteger(0);
    /**
     * 获取远程客户端
     *
     * @return
     */
    public PooledRemoteClient getRemoteClient() throws BaseAppException {
        try {
            // 从空闲队列中获取
            RemoteClientHolder holder = queue.poll(0, TimeUnit.MILLISECONDS);
            if (holder != null) {
                return new PooledRemoteClient(holder);
            }

            // 尝试创建新的客户端
            if (counting.incrementAndGet() <= maxPoolSize) {
                RemoteClient remoteClient = null;
                try {
                    remoteClient = createRemoteClient();
                    holder = new RemoteClientHolder(this, remoteClient);
                    PooledRemoteClient pooledRemoteClient = new PooledRemoteClient(holder);
                    clients.add(remoteClient);
                    return pooledRemoteClient;
                }
                catch (Exception e) {
                    counting.decrementAndGet();
                    if (remoteClient != null) {
                        remoteClient.close();
                    }
                    holder = null;
                    ExceptionPublisher.publish(e, CmdbExceptionErrorCode.REMOTE_GET_CLIENT_FAILED, this.hostAccessInfo.getHost());
                }
            }
            else {
                counting.decrementAndGet();
            }
            // 如果客户端数目已达到允许的最大值，则等待其他线程释放
            holder = queue.poll(timeout, TimeUnit.MILLISECONDS);
            if (holder != null) {
                return new PooledRemoteClient(holder);
            }
        }
        catch (InterruptedException e) {
            ExceptionPublisher.publish(e, CmdbExceptionErrorCode.REMOTE_GET_CLIENT_FAILED, this.hostAccessInfo.getHost());
        }
        ExceptionPublisher.publish(CmdbExceptionErrorCode.REMOTE_GET_CLIENT_TIMEOUT, this.hostAccessInfo.getHost());
        return null;
    }

    /**
     * 创建一个远程客户端
     *
     * @return
     */
    private RemoteClient createRemoteClient() throws BaseAppException {
        ExecRemoteClient remoteClient = new ExecRemoteClient(this.hostAccessInfo);
        remoteClient.connect();
        return remoteClient;
    }

    /**
     * 回收远程客户端
     *
     * @param remoteClient
     */
    public void recycle(PooledRemoteClient remoteClient) {
        this.queue.add(remoteClient.getHolder());
    }

    /**
     * 检查是否存在客户端空闲时间超过设置的最大值
     */
    public void checkIdle() {
        if (this.queue.isEmpty()) {
            return;
        }
        List<RemoteClientHolder> holders = new ArrayList<>();
        this.queue.drainTo(holders);
        for (RemoteClientHolder holder:holders) {
            if (cleanable(holder)) {
                holder.setPool(null);
                RemoteClient remoteClient = holder.getRemoteClient();
                remoteClient.close();
                this.clients.remove(remoteClient);
                holder.setRemoteClient(null);
                // 更新客户端数
                this.counting.decrementAndGet();

            }
            else {
                this.queue.add(holder);
            }
        }
    }

    /**
     * 指定客户端是否可清理，
     * 当客户端数目大于minPoolSize,并且空闲时间超过最大空闲时间时，会被清理
     *
     * @param holder
     * @return
     */
    private boolean cleanable(RemoteClientHolder holder) {
        if (this.clients.size() <= this.minPoolSize) {
            return false;
        }
        if (System.currentTimeMillis() - holder.getLastActiveTime() <= this.maxIdleTime) {
            return false;
        }
        return true;
    }

    /**
     * 关闭远程客户端池
     */
    public void close() {
        this.clients.forEach(client -> client.close());
    }

    @Override
    public String toString() {
        return "RemoteClientPool{" +
                "minPoolSize=" + minPoolSize +
                ", maxPoolSize=" + maxPoolSize +
                ", maxIdleTime=" + maxIdleTime +
                ", timeout=" + timeout +
                ", host=" + hostAccessInfo.getHost()+
                '}';
    }
}
